package com.fowo.api.sys.component;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import cn.hutool.http.HttpUtil;
import cn.hutool.script.ScriptUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fowo.api.common.excel.ToMapListener;
import com.fowo.api.common.model.PageSearch;
import com.fowo.api.common.model.RException;
import com.fowo.api.common.util.ContextHolder;
import com.fowo.api.common.util.RequestUtils;
import com.fowo.api.config.AppProperties;
import com.fowo.api.sys.entity.SysTaskDataSource;
import com.fowo.api.sys.mapper.SysTaskDataSourceMapper;
import com.fowo.api.sys.service.SysDevService;
import com.fowo.api.sys.service.SysLogService;
import com.fowo.api.user.model.JwtUserInfo;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.SimpleScriptContext;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

// BEE_MAKE_IMPORT

import static javax.script.ScriptContext.ENGINE_SCOPE;

/**
 * 调用js代码提供sql和服务支持
 *
 * @author
 */
@Slf4j
@Component
public class ExecuteScriptEngine {
    @Resource
    private SysDevService devService;

    @Resource
    private AppProperties appProperties;

    @Resource
    private JavaMailSender mailSender;

    @Value("${spring.mail.username}")
    private String fromUsername;

    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Resource
    private SysTaskDataSourceMapper sysTaskDataSourceMapper;

    private SysLogService sysLogService;

    private JdbcTemplate jdbcTemplate;

    public JdbcTemplate getJdbcTemplate() {
        if (jdbcTemplate == null) {
            jdbcTemplate = new JdbcTemplate(ContextHolder.getApplicationContext().getBean(DataSource.class));
        }
        return jdbcTemplate;
    }

    private final Map<String, JdbcTemplate> namedJdbcTemplates = new HashMap<>();

    public JdbcTemplate getJdbcTemplate(String name) {
        if (!StringUtils.hasText(name)) {
            return getJdbcTemplate();
        }
        return namedJdbcTemplates.computeIfAbsent(name, n -> {
            SysTaskDataSource taskDataSource = sysTaskDataSourceMapper.selectOne(Wrappers.<SysTaskDataSource>query().eq("name", n));
            if (taskDataSource == null) {
                throw new RuntimeException(String.format("Datasource name %s is not found!", n));
            }
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName(taskDataSource.getDriver());
            dataSource.setUrl(taskDataSource.getUrl());
            dataSource.setUsername(taskDataSource.getUsername());
            dataSource.setPassword(taskDataSource.getPassword());
            // DynamicMultiDataSourceTransactionalAop.addDataSource(dataSource, false);
            return new JdbcTemplate(dataSource);
        });
    }

    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    private Deque<String> userMessage = new LinkedList<>();

    public Deque<String> getUserMessage() {
        return userMessage;
    }

    public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
        return ObjectUtil.isNotEmpty(namedParameterJdbcTemplate) ? namedParameterJdbcTemplate : new NamedParameterJdbcTemplate(ContextHolder.getApplicationContext().getBean(DataSource.class));
    }

    public SysLogService getSysLogService() {
        if (sysLogService == null) {
            sysLogService = ContextHolder.getApplicationContext().getBean(SysLogService.class);
        }
        return sysLogService;
    }


    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 缓存键前缀
     */
    protected static final String CACHE_KEY_PREFIX = "ExecuteScript:CACHE_KEY:";

    // 引入 ID 生成算法
    private final IdentifierGenerator identifierGenerator = new DefaultIdentifierGenerator();


    private static RestTemplate RT;

    static {
        SSLContextBuilder builder = new SSLContextBuilder();
        try {
            builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (KeyStoreException e) {
            throw new RuntimeException(e);
        }
        SSLConnectionSocketFactory sslConnectionSocketFactory = null;
        try {
            sslConnectionSocketFactory = new SSLConnectionSocketFactory(builder.build(), NoopHostnameVerifier.INSTANCE);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (KeyManagementException e) {
            throw new RuntimeException(e);
        }

        CloseableHttpClient httpClient
                = HttpClients.custom()
                .setSSLHostnameVerifier(new NoopHostnameVerifier())
                .setSSLSocketFactory(sslConnectionSocketFactory)
                .build();
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setHttpClient(httpClient);
        requestFactory.setConnectTimeout(60000 * 5);
        requestFactory.setReadTimeout(60000 * 5);
        RT = new RestTemplate(requestFactory);
        // 解决乱码问题
        RT.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
    }

    /**
     * 调用资源文件中的 js 代码入口
     *
     * @param scriptResPath 资源文件路径
     * @param args          参数
     * @param otherArgs     其它参数
     * @param service       所在服务
     * @return 脚本返回值
     * @throws Exception 异常
     */
    public Object callJsInResource(String scriptResPath, Object args, OtherArgs otherArgs, ServiceImpl<?, ?> service) throws Exception {
        String script = devService.getScriptByName(scriptResPath);
        return callJs(script, args, otherArgs, service);
    }


    /**
     * 调用资源文件中的 js 代码入口
     *
     * @param scriptResPath 资源文件路径
     * @param args          参数
     * @param service       所在服务
     * @return 脚本返回值
     * @throws Exception 异常
     */
    public Object callJsInResource(String scriptResPath, Object args, ServiceImpl<?, ?> service) throws Exception {
        String script = devService.getScriptByName(scriptResPath);
        return callJs(script, args, null, service);
    }


    /**
     * 调用js代码,入口
     *
     * @param script    数据处理函数
     * @param args      参数
     * @param otherArgs 其它参数
     * @param service   所在服务
     * @return 脚本返回值
     * @throws Exception 异常
     */
    public Object callJs(String script, Object args, OtherArgs otherArgs, ServiceImpl<?, ?> service) throws Exception {
        //调用engine执行
        System.setProperty("nashorn.args", "--language=es6");
        final ScriptEngine engine = ScriptUtil.getJsEngine();
        final SimpleScriptContext ctx = new SimpleScriptContext();
        ctx.setAttribute("lib", new ScriptHelperLib(service, args, otherArgs), ENGINE_SCOPE);
        ctx.setAttribute("args", args, ENGINE_SCOPE);
        Bindings engineBindings = ctx.getBindings(ENGINE_SCOPE);
        ctx.setBindings(engineBindings, ENGINE_SCOPE);
        engine.setContext(ctx);
        Object res = ScriptUtil.eval(StrUtil.format("load('classpath:scripts/xe-utils.js');{}", script));
        res = convertJSType(res);
        return res;
    }


    /**
     * 附加参数类
     */
    public static class OtherArgs {
        private List<Map<String, Object>> uploadData;

        public List<Map<String, Object>> getUploadData() {
            return uploadData;
        }

        /**
         * 上传文件转换为 uploadData 数据
         *
         * @param file 上传的文件
         * @return 附加参数自身
         */
        public OtherArgs applyUploadData(MultipartFile file) {
            if (file != null) {
                try {
                    List<Map<String, Object>> listMap = EasyExcel.read(file.getInputStream())
                            .useDefaultListener(false)
                            .sheet()
                            .registerReadListener(new ToMapListener())
                            .doReadSync();
                    uploadData = listMap;
                } catch (IOException e) {
                    throw new RuntimeException("读取上传文件时发生错误");
                }
            }
            return this;
        }
    }


    public class ScriptHelperLib {
        private final ServiceImpl<?, ?> service;
        private final Object args;
        private final OtherArgs otherArgs;

        public ScriptHelperLib(ServiceImpl<?, ?> service, Object args, OtherArgs otherArgs) {
            this.service = service;
            this.args = args;
            this.otherArgs = otherArgs;
        }

        /**
         * 功能描述：获取上传 Excel 中的数据
         *
         * @return 参数表示的查询主键列表
         */
        public List<Map<String, Object>> getUploadData() {
            if (otherArgs != null) {
                return otherArgs.getUploadData();
            }
            return null;
        }

        /**
         * 功能描述：处理ids
         *
         * @return 参数表示的查询主键列表
         */
        @SuppressWarnings("unused")
        public List<String> eachId() {
            return this.eachId(args);
        }

        /**
         * 功能描述：处理ids
         *
         * @param param 入口参数
         * @return 参数表示的查询主键列表
         */
        @SuppressWarnings("unused")
        public List<String> eachId(Object param) {
            List<String> res;
            if (param instanceof Number || param instanceof String) {
                res = toStringList(param);
            } else if (param instanceof List) {
                res = toStringList(param);
            } else if (param instanceof PageSearch || args instanceof PageSearch) {
                Class<?> mapperClass = AopProxyUtils.ultimateTargetClass(service.getBaseMapper());
                Method method = ReflectionUtils.findMethod(mapperClass, "searchIds", param.getClass());
                Assert.notNull(method, "方法名错误！");
                Object r = null;
                try {
                    r = ReflectionUtils.invokeMethod(method, service.getBaseMapper(), param);
                } catch (Exception ex) {
                    throw new RuntimeException(StrUtil.format("执行：eachId 参数： {} 异常，异常信息： {}", param, ExceptionUtil.getSimpleMessage(ex)));
                }
                res = toStringList(r);
            } else {
                throw new IllegalArgumentException("参数指定错误");
            }
            return res;
        }

        @SuppressWarnings("unused")
        private List<String> toStringList(Object param) {
            if (param instanceof Number) {
                return Collections.singletonList(StrUtil.concat(false, param.toString()));
            }
            if (param instanceof String) {
                return Collections.singletonList(StrUtil.concat(false, (String) param));
            }
            List<?> list = (List<?>) param;
            return list.stream().map(m -> StrUtil.concat(false, String.valueOf(m))).collect(Collectors.toList());
        }

        /**
         * 功能描述：执行查询 ? 的形式
         *
         * @param sql   查询语句
         * @param param 参数列表
         * @return 结果集
         */
        @SuppressWarnings("unused")
        public List<Map<String, Object>> dbQuery(String sql, Object... param) {
            param = convertFilter(null, param);
            ScriptSqlPair sqlPair = getSqlPair(sql);
            jdbcTemplate = getJdbcTemplate(sqlPair.db);
            if (ObjectUtil.isEmpty(jdbcTemplate)) {
                return null;
            }
            try {
                if (ArrayUtil.isEmpty(param)) {
                    return jdbcTemplate.queryForList(sqlPair.sql);
                }
                return jdbcTemplate.queryForList(sqlPair.sql, param);
            } catch (Exception ex) {
                throw new RuntimeException(StrUtil.format("查询SQL：{} 参数： {} 异常，异常信息： {}", sql, JSON.toJSONString(param, SerializerFeature.WriteMapNullValue), ExceptionUtil.getRootCauseMessage(ex)));
            }
        }

        /**
         * 功能描述：执行查询 ? 的形式
         *
         * @param sql   查询语句
         * @param param 参数列表
         * @return 结果集
         */
        @SuppressWarnings("unused")
        public JSONArray dbQueryJsonArray(String sql, Object... param) {
            return JSON.parseArray(JSON.toJSONString(dbQuery(sql, param), (ValueFilter) (object, name, value) -> value instanceof Long ? StrUtil.toStringOrNull(value) : value, SerializerFeature.WriteMapNullValue));
        }


        /**
         * 查询单个字段
         *
         * @param sql
         * @param param
         * @return
         */
        @SuppressWarnings("unused")
        public Object dbQueryField(String sql, Object... param) {
            List<Map<String, Object>> list = dbQuery(sql, param);
            if (CollUtil.isEmpty(list)) {
                return null;
            }
            Map<String, Object> map = CollUtil.getFirst(list);
            if (MapUtil.isEmpty(map)) {
                return null;
            }
            return CollUtil.getFirst(map.values());
        }


        /**
         * 功能描述：执行插入、更新
         *
         * @param sql   查询语句
         * @param param 参数列表
         * @return 变更数量
         */
        @SuppressWarnings("unused")
        public int dbUpdate(String sql, Object... param) {
            param = convertFilter(null, param);
            ScriptSqlPair sqlPair = getSqlPair(sql);
            jdbcTemplate = getJdbcTemplate(sqlPair.db);
            if (ObjectUtil.isEmpty(jdbcTemplate)) {
                return 0;
            }
            try {
                if (ArrayUtil.isEmpty(param)) {
                    return jdbcTemplate.update(sqlPair.sql);
                }
                return jdbcTemplate.update(sqlPair.sql, param);
            } catch (Exception ex) {
                throw new RuntimeException(StrUtil.format("执行SQL：{} 参数： {} 异常，异常信息： {}", sql, JSON.toJSONString(param, SerializerFeature.WriteMapNullValue), ExceptionUtil.getRootCauseMessage(ex)));
            }
        }



        @SuppressWarnings("unused")
        public List<Map<String, Object>> dbNamedParameterQuery(String sql, Map<String, ?> param) {
            param = (Map<String, ?>) convertJSType(param);
            namedParameterJdbcTemplate = getNamedParameterJdbcTemplate();
            if (ObjectUtil.isEmpty(namedParameterJdbcTemplate)) {
                return null;
            }
            List<Map<String, Object>> list = namedParameterJdbcTemplate.queryForList(sql, param);
            return list;
        }

        /**
         * 功能描述：执行查询 :id 的形式
         *
         * @param sql   查询语句
         * @param param 参数列表
         * @return 结果集
         */
        @SuppressWarnings("unused")
        public JSONArray dbNamedParameterQueryJsonArray(String sql, Map<String, ?> param) {
            try {
                List<Map<String, Object>> list = dbNamedParameterQuery(sql, param);
                return JSON.parseArray(JSON.toJSONString(list, (ValueFilter) (object, name, value) -> value instanceof Long ? StrUtil.toStringOrNull(value) : value, SerializerFeature.WriteMapNullValue));
            } catch (Exception ex) {
                throw new RuntimeException(StrUtil.format("查询SQL：{} 参数： {} 异常，异常信息： {}", sql, JSON.toJSONString(param, SerializerFeature.WriteMapNullValue), ExceptionUtil.getRootCauseMessage(ex)));
            }
        }


        /**
         * 查询单个字段
         *
         * @param sql
         * @param param
         * @return
         */
        @SuppressWarnings("unused")
        public Object dbNamedParameterQueryField(String sql, Map<String, ?> param) {
            List<Map<String, Object>> list = dbNamedParameterQuery(sql, param);
            if (list == null) return null;
            if (CollUtil.isEmpty(list)) {
                return null;
            }
            Map<String, Object> map = CollUtil.getFirst(list);
            if (MapUtil.isEmpty(map)) {
                return null;
            }
            return CollUtil.getFirst(map.values());
        }


        /**
         * 功能描述：执行插入、更新
         *
         * @param sql   查询语句
         * @param param 参数列表
         * @return 变更数量
         */
        @SuppressWarnings("unused")
        public int dbNamedParameterUpdate(String sql, Map<String, ?> param) {
            param = (Map<String, ?>) convertJSType(param);
            namedParameterJdbcTemplate = getNamedParameterJdbcTemplate();
            if (ObjectUtil.isEmpty(namedParameterJdbcTemplate)) {
                return 0;
            }
            try {
                return namedParameterJdbcTemplate.update(sql, param);
            } catch (Exception ex) {
                throw new RuntimeException(StrUtil.format("执行SQL：{} 参数： {} 异常，异常信息： {}", sql, JSON.toJSONString(param, SerializerFeature.WriteMapNullValue), ExceptionUtil.getRootCauseMessage(ex)));
            }
        }

        /**
         * 功能描述：执行批量插入、更新
         *
         * @param sql        查询语句
         * @param paramsList 参数列表
         * @return 变更数量
         */
        @SuppressWarnings("unused")
        public int[] dbNamedParameterBatchUpdate(String sql, Map<String, ?>[] paramsList) {
            paramsList = (Map<String, ?>[]) convertJSType(paramsList);
            namedParameterJdbcTemplate = getNamedParameterJdbcTemplate();
            if (ObjectUtil.isEmpty(namedParameterJdbcTemplate)) {
                return new int[0];
            }
            try {
                return namedParameterJdbcTemplate.batchUpdate(sql, paramsList);
            } catch (Exception ex) {
                throw new RuntimeException(StrUtil.format("执行SQL：{} 参数： {} 异常，异常信息： {}", sql, JSON.toJSONString(paramsList, SerializerFeature.WriteMapNullValue), ExceptionUtil.getRootCauseMessage(ex)));
            }
        }

        /**
         * 功能描述：记录error日志
         *
         * @param msg   异常信息
         * @param param 参数列表
         */
        @SuppressWarnings("unused")
        public void exception(String msg, Object... param) {
            Assert.isTrue(false, msg, param);
        }


        /**
         * 功能描述：记录info日志
         *
         * @param msg   异常信息
         * @param param 参数列表
         */
        @SuppressWarnings("unused")
        public void log(String msg, Object... param) {
            log.error(msg, param);
        }

        /**
         * 功能描述：发送请求
         *
         * @param url     地址
         * @param param   参数列表
         * @param headers 请求头
         * @return 变更数量
         */
        @SuppressWarnings("unused")
        public Object send(String url, String method, Object param, Object headers) {
            Assert.isTrue(StrUtil.isAllNotBlank(url, method) && (HttpUtil.isHttp(url) || HttpUtil.isHttps(url)), StrUtil.format("参数校验不通过，请检查"));
            try {
                param = convertJSType(param);
                headers = convertJSType(headers);
                if (StrUtil.equalsIgnoreCase(HttpMethod.GET.name(), method)) {
                    if (ArrayUtil.isNotEmpty(param)) {
                        url = HttpUtil.urlWithForm(url, Convert.convert(Map.class, param), Charset.defaultCharset(), true);
                    }
                }
                HttpHeaders head = new HttpHeaders();
                if (ObjectUtil.isNotEmpty(headers)) {
                    head.setAll(Convert.convert(Map.class, headers));
                }
                HttpMethod httpMethod = EnumUtil.likeValueOf(HttpMethod.class, method.toUpperCase());
                Object res = httpRequest(url, httpMethod, param, head);
                return res;
            } catch (Exception ex) {
                throw new RuntimeException(StrUtil.format("执行接口：{} 参数： {} 异常，异常信息： {}", url, JSON.toJSONString(param, SerializerFeature.WriteMapNullValue), ExceptionUtil.getRootCauseMessage(ex)));
            }
        }


        /**
         * 设置redis秒级缓存
         *
         * @param key
         * @param object
         * @param time
         * @return
         */
        @SuppressWarnings("unused")
        public void setCache(String key, Object object, String time) {
            Assert.isTrue(StrUtil.isNotBlank(key) && ObjectUtil.isNotEmpty(object), StrUtil.format("参数校验不通过，请检查"));
            key = StrUtil.concat(true, CACHE_KEY_PREFIX, key);
            try {
                insertStr(key, object, Convert.toLong(time));
            } catch (Exception ex) {
                throw new RuntimeException(StrUtil.format("执行设置缓存：{} 参数： {} 异常，异常信息： {}", key, JSON.toJSONString(object, SerializerFeature.WriteMapNullValue), ExceptionUtil.getRootCauseMessage(ex)));
            }
        }

        /**
         * 获取redis列表缓存的长度
         * @param key 缓存键名
         * @return 缓存长度
         */
        @SuppressWarnings("unused")
        public Long getRedisListSize(String key) {
            Assert.isTrue(StrUtil.isNotBlank(key), StrUtil.format("参数校验不通过，请检查"));
            return redisTemplate.opsForList().size(key);
        }

        /**
         * 获取redis秒级缓存
         *
         * @param key
         */
        @SuppressWarnings("unused")
        public String getCache(String key) {
            Assert.isTrue(StrUtil.isNotBlank(key), StrUtil.format("参数校验不通过，请检查"));
            key = StrUtil.concat(true, CACHE_KEY_PREFIX, key);
            try {
                return StrUtil.toStringOrNull(redisTemplate.opsForValue().get(key));
            } catch (Exception ex) {
                throw new RuntimeException(StrUtil.format("执行获取缓存：{} 异常，异常信息： {}", key, ExceptionUtil.getRootCauseMessage(ex)));
            }
        }

        /**
         * 删除redis秒级缓存
         *
         * @param key
         * @return
         */
        @SuppressWarnings("unused")
        public void removeCache(String key) {
            Assert.isTrue(StrUtil.isNotBlank(key), StrUtil.format("参数校验不通过，请检查"));
            key = StrUtil.concat(true, CACHE_KEY_PREFIX, key);
            try {
                if (redisTemplate.hasKey(key)) {
                    redisTemplate.delete(key);
                }
            } catch (Exception ex) {
                throw new RuntimeException(StrUtil.format("执行删除缓存：{} 异常，异常信息： {}", key, ExceptionUtil.getRootCauseMessage(ex)));
            }
        }


        /**
         * 功能描述：执行服务方法
         *
         * @param serviceName   服务名称
         * @param serviceMethod 服务方法
         * @param param         调用参数
         * @return 返回
         */
        @SuppressWarnings("unused")
        public Object serviceInvoke(String serviceName, String serviceMethod, Object... param) {
            Assert.isTrue(StrUtil.isAllNotBlank(serviceName, serviceMethod), "调用参数异常！");
            Object bean = "current".equals(serviceName) ? service : ContextHolder.getApplicationContext().getBean(serviceName);
            Assert.notNull(bean, "服务不存在！");
            Method method = ReflectUtil.getMethodByName(bean.getClass(), serviceMethod);
            Assert.notNull(method, "方法名错误！");
            param = convertFilter(method, param);
            try {
                Object res = ReflectUtil.invoke(bean, method, param);
                return res;
            } catch (Exception ex) {
                throw new RuntimeException(StrUtil.format("执行服务：{} 方法：{} 参数： {} 异常，异常信息： {}", serviceName, serviceMethod, JSON.toJSONString(param, SerializerFeature.WriteMapNullValue), ExceptionUtil.getRootCauseMessage(ex)));
            }
        }


        /**
         * 功能描述：js开启事务
         *
         * @return 返回 事务对象
         */
        @SuppressWarnings("unused")
        public Object newTransaction() {
            DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
            transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_NESTED);
            TransactionStatus txs = dataSourceTransactionManager.getTransaction(transactionDefinition);
            // DynamicMultiDataSourceTransactionalAop.add(dataSourceTransactionManager, txs);
            JSTransaction jsTransaction = new JSTransaction(txs);
            return jsTransaction;
        }


        /**
         * 功能描述：执行服务方法无事务
         *
         * @param serviceName   服务名称
         * @param serviceMethod 服务方法
         * @param param         调用参数
         * @return 返回
         */
        @SuppressWarnings("unused")
        public Object serviceInvokeNoTrans(String serviceName, String serviceMethod, Object... param) {
//          Future<?> future = ThreadUtil.execAsync(() -> serviceInvoke(serviceName, serviceMethod, param));
//          return future.get();
            TransactionStatus txs = null;
            Object res = null;
            try {
                DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
                transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
                txs = dataSourceTransactionManager.getTransaction(transactionDefinition);
                res = serviceInvoke(serviceName, serviceMethod, param);
                dataSourceTransactionManager.commit(txs);
            } catch (Exception ex) {
                if (ObjectUtil.isNotEmpty(txs)) {
                    dataSourceTransactionManager.rollback(txs);
                }
            }
            return res;
        }

        /**
         * 包装事务对象
         */
        public class JSTransaction {
            TransactionStatus txs;

            public JSTransaction(TransactionStatus _txs) {
                this.txs = _txs;
            }

            public void commit() {
                dataSourceTransactionManager.commit(txs);

            }

            public void rollback() {
                if (ObjectUtil.isNotEmpty(txs)) {
                    dataSourceTransactionManager.rollback(txs);
                }
            }
        }

        /**
         * 功能描述：获取当前登陆用户
         *
         * @return 结果集
         */
        @SuppressWarnings("unused")
        public String currentUserId() {
            JwtUserInfo jwtUserInfo = JwtUserInfo.fromHeader();
            return null == jwtUserInfo ? null : StrUtil.concat(false, StrUtil.toString(jwtUserInfo.getUserId()));
        }

        /**
         * 功能描述：取新 id
         *
         * @return 结果集
         */
        @SuppressWarnings("unused")
        public String newId() {
            // 获取一个新的 ID
            return StrUtil.concat(false, StrUtil.toString(identifierGenerator.nextId(null)));
        }

        /**
         * 功能描述：收集用户信息
         *
         * @return 结果集
         */
        @SuppressWarnings("unused")
        public int addUserMessage(String msg) {
            if (StrUtil.isNotBlank(msg)) {
                userMessage.addLast(msg);
                return 1;
            }
            return 0;
        }

        /**
         * 发送系统邮件
         * @param params 发邮件参数
         * @return 发送是否成功
         */
        @SuppressWarnings("unused")
        public void sendMail(Object params) throws MessagingException, UnsupportedEncodingException {
            if (params == null) {
                throw new RuntimeException("发件参数不能为空");
            }
            SendMailParams sendMailParams = JSON.toJavaObject((JSON) JSON.toJSON(params), SendMailParams.class);
            if (!StringUtils.hasText(sendMailParams.toAddress)) {
                throw new RuntimeException("toAddress 不能为空！");
            }
            if (!StringUtils.hasText(sendMailParams.subject)) {
                throw new RuntimeException("subject 不能为空！");
            }
            if (!StringUtils.hasText(sendMailParams.html)) {
                throw new RuntimeException("html 不能为空！");
            }

            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            if (StringUtils.hasText(sendMailParams.to)) {
                helper.setTo(new InternetAddress(sendMailParams.toAddress, sendMailParams.to));
            } else {
                helper.setTo(sendMailParams.toAddress);
            }
            helper.setSubject(sendMailParams.subject);
            helper.setText(sendMailParams.html, true);
            message.setFrom(fromUsername);
            mailSender.send(message);
        }

        // BEE_MAKE_SCRIPT_HELPER_LIB
    }

    @Getter
    @Setter
    static class SendMailParams {
        private String to;
        private String toAddress;
        private String subject;
        private String html;
    }

    /**
     * 功能描述：参数转换
     *
     * @param method
     * @param param
     * @return
     */
    @SuppressWarnings("unused")
    private static Object[] convertFilter(Method method, Object[] param) {
        param = ArrayUtil.length(param) == 1 ? ArrayUtil.removeNull(param) : param;
        if (ArrayUtil.isEmpty(param)) {
            return null;
        }
        if (ObjectUtil.isEmpty(method)) {
            return param;
        }
        Class<?>[] paramClasses = TypeUtil.getParamClasses(method);
        Assert.isTrue(ArrayUtil.length(paramClasses) == ArrayUtil.length(param), "实参与入参数量不匹配！");
        int i = 0;
        for (Class<?> item : paramClasses) {
            //处理类型
            param[i] = Convert.convert(item, convertJSType(param[i]));
            i = i + 1;
        }
        return param;
    }

    /**
     * 参数或返回值转换
     *
     * @param obj
     * @return
     */
    public static Object convertJSType(Object obj) {
        if (obj instanceof ScriptObjectMirror) {
            ScriptObjectMirror mirror = (ScriptObjectMirror) obj;
            if (mirror.isEmpty()) {
                return null;
            }
            if (mirror.isArray()) {
                List<Object> list = new ArrayList<>();
                for (Map.Entry<String, Object> entry : mirror.entrySet()) {
                    Object result = entry.getValue();
                    if (result instanceof ScriptObjectMirror) {
                        list.add(convertJSType((ScriptObjectMirror) result));
                    } else {
                        list.add(result);
                    }
                }
                return list;
            }
            Map<String, Object> map = new HashMap<>();
            for (Map.Entry<String, Object> entry : mirror.entrySet()) {
                Object result = entry.getValue();
                if (result instanceof ScriptObjectMirror) {
                    map.put(entry.getKey(), convertJSType((ScriptObjectMirror) result));
                } else {
                    map.put(entry.getKey(), result);
                }
            }
            return map;
        }
        return obj;
    }


    /**
     * 构造
     *
     * @return
     */
    public RestTemplate create() {
        if (ObjectUtil.isEmpty(RT)) {
            return new RestTemplate();
        }
        return RT;
    }

    /**
     * 发送请求
     *
     * @param url     请求地址
     * @param method  请求方式
     * @param headers 请求头  可空
     * @param params  请求body参数 可空
     * @return ResponseEntity<responseType>
     */
    public <T> T httpRequest(String url, HttpMethod method, Object params, HttpHeaders headers, Class<T> clazz) {
        try {
            if (StrUtil.isEmpty(url)) {
                Assert.isTrue(false, "baseUrl 不能为空");
            }
            if (ObjectUtil.isEmpty(method)) {
                Assert.isTrue(false, "method 不能为空");
            }
            if (ObjectUtil.isEmpty(headers)) {
                headers = new HttpHeaders();
            }
            String contentType = MapUtil.getStr(headers, Header.CONTENT_TYPE.getValue());
            if (StrUtil.isBlank(contentType)) {
                headers.set(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue());
            }
            // 请求体
            String body = "";
            if (ObjectUtil.isNotEmpty(params)) {
                if (params instanceof JSONObject || params instanceof Map) {
                    body = JSON.toJSONString(params, SerializerFeature.WriteMapNullValue);
                } else {
                    body = params.toString();
                }
            }
            // 发送请求
            HttpEntity<String> request = new HttpEntity<>(body, headers);
            T res = RT.exchange(url, method, request, clazz).getBody();
            return res;
        } catch (Exception e) {
            Assert.isTrue(false, "HTTP调用错误 ，错误提示:" + StrUtil.subPre(ExceptionUtil.getRootCauseMessage(e), 1000));
        }
        return null;
    }

    /**
     * 发送请求
     *
     * @param url     请求地址
     * @param method  请求方式
     * @param headers 请求头  可空
     * @param params  请求body参数 可空
     * @return ResponseEntity<responseType>
     */
    public Object httpRequest(String url, HttpMethod method, Object params, HttpHeaders headers) {
        return httpRequest(url, method, params, headers, String.class);
    }

    /**
     * 插入字符串缓存(有时间,默认一个月)
     *
     * @param key
     * @param object
     */
    public void insertStr(String key, Object object, long time) {
        if (object instanceof Integer || object instanceof Long || object instanceof Float ||
                object instanceof Double || object instanceof Boolean || object instanceof String) {
            redisTemplate.opsForValue().set(key, StrUtil.toStringOrNull(object));
        } else {
            redisTemplate.opsForValue().set(key, JSON.toJSONString(object, SerializerFeature.WriteMapNullValue));
        }
        if (time > 0) {
            redisTemplate.expire(key, time, TimeUnit.SECONDS);
        } else {
            redisTemplate.expire(key, TimeUnit.SECONDS.toSeconds(60 * 60 * 8 * 30), TimeUnit.SECONDS);
        }
    }

    /**
     * JdbcTemplate批量构建
     */
    public class BatchUpdateSetter implements BatchPreparedStatementSetter {

        private List<Object[]> paramsList = new ArrayList<Object[]>();

        public BatchUpdateSetter(List<Object[]> paramsList) {
            this.paramsList = paramsList;
        }

        @Override
        public void setValues(PreparedStatement ps, int i) throws SQLException {
            Object[] params = paramsList.get(i);
            for (int j = 0; j < params.length; j++) {
                ps.setObject(j + 1, params[j]);
            }
        }

        @Override
        public int getBatchSize() {
            return paramsList.size();
        }

    }

    /**
     * 请求参数拼装到args
     *
     * @param args
     */

    public static void requestParamUnion(JSONObject args) {
        HttpServletRequest request = RequestUtils.getRequest();
        if (ObjectUtil.isNotNull(request)) {
            Map<String, String> paramMap = ServletUtil.getParamMap(request);
            if (MapUtil.isNotEmpty(paramMap)) {
                args.putAll(paramMap);
            }
        }
    }

    private ScriptSqlPair getSqlPair(String sql) {
        ScriptSqlPair pair = new ScriptSqlPair();
        if (sql.startsWith("[")) {
            int end = sql.indexOf(']');
            if (end > -1) {
                pair.db = sql.substring(1, end);
                pair.sql = sql.substring(end + 1);
                return pair;
            }
        }
        pair.sql = sql;
        return pair;
    }

    static class ScriptSqlPair {
        public String db;
        private String sql;
    }

}


